package webx

import (
	"fmt"
	"reflect"
	"regexp"
	"sort"
	"strings"

	"gitee.com/xungen/goweb/errno"
	"gitee.com/xungen/goweb/utils"
)

type fieldItem struct {
	name     string
	clazz    reflect.Type
	regex    *regexp.Regexp
	option   []string
	remark   string
	rangeT   int64
	rangeX   int64
	rangeY   int64
	rangeXF  float64
	rangeYF  float64
	required bool
}

type document struct {
	path         string
	group        string
	access       string
	remark       string
	request      string
	response     string
	fieldList    []fieldItem
	requestType  reflect.Type
	responseType reflect.Type
}

var docmap = make(map[string]*document)

func (doc *document) Path(path string) *document {
	doc.path = path
	return doc
}

func (doc *document) Group(group string) *document {
	doc.group = group
	return doc
}

func (doc *document) Remark(remark string) *document {
	doc.remark = remark
	return doc
}

func (doc *document) Access(access string) *document {
	doc.access = access
	return doc
}

func (item *fieldItem) checkValue(val string) {
	if val == "" {
		if item.required {
			errno.PARAMERR.Panic("parameter[" + item.name + "] missing")
		}
	} else {
		item.checkRange(val)
		item.checkOption(val)
		if item.regex == nil || item.regex.MatchString(val) {
			return
		}
		errno.PARAMERR.Panic("parameter[" + item.name + "] format error")
	}
}

func (item *fieldItem) checkRange(val string) {
	switch item.rangeT {
	case 0:
		return
	case 1:
		if v := errno.IgnoreError(utils.ToInteger[int64](val)); v > item.rangeX && v < item.rangeY {
			return
		}
	case 2:
		if v := errno.IgnoreError(utils.ToInteger[int64](val)); v > item.rangeX && v <= item.rangeY {
			return
		}
	case 3:
		if v := errno.IgnoreError(utils.ToInteger[int64](val)); v >= item.rangeX && v < item.rangeY {
			return
		}
	case 4:
		if v := errno.IgnoreError(utils.ToInteger[int64](val)); v >= item.rangeX && v <= item.rangeY {
			return
		}
	case -1:
		if v := errno.IgnoreError(utils.ToFloat(val)); v > item.rangeXF && v < item.rangeYF {
			return
		}
	case -2:
		if v := errno.IgnoreError(utils.ToFloat(val)); v > item.rangeXF && v <= item.rangeYF {
			return
		}
	case -3:
		if v := errno.IgnoreError(utils.ToFloat(val)); v >= item.rangeXF && v < item.rangeYF {
			return
		}
	case -4:
		if v := errno.IgnoreError(utils.ToFloat(val)); v >= item.rangeXF && v <= item.rangeYF {
			return
		}
	}

	errno.PARAMERR.Panic("parameter[" + item.name + "] invalid")
}

func (item *fieldItem) checkOption(val string) {
	if len(item.option) == 0 {
		return
	}
	for _, v := range item.option {
		if v == val {
			return
		}
	}
	errno.PARAMERR.Panic("parameter[" + item.name + "] invalid")
}

func (item *fieldItem) getOptionString() string {
	if item.required {
		return "required"
	} else {
		return "optional"
	}
}

func getString(clazz reflect.Type, fieldlist *[]fieldItem) string {
	doc := ""
	fieldnum := clazz.NumField()

	for i := 0; i < fieldnum; i++ {
		field := clazz.Field(i)
		item := newFieldItem(field)
		if kind := item.clazz.Kind(); utils.IsBaseValue(kind) {
			doc += fmt.Sprintf("\n<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>", item.name, item.clazz.Name(), item.getOptionString(), item.remark)
		} else if kind == reflect.Slice || kind == reflect.Array {
			subclazz := field.Type.Elem()
			doc += fmt.Sprintf("\n<tr><td><span>%s</span></td><td>%s</td><td>%s</td><td>%s</td></tr>", item.name, "array", item.getOptionString(), item.remark)
			if kind := subclazz.Kind(); kind == reflect.Slice || kind == reflect.Array || kind == reflect.Struct {
				doc += strings.ReplaceAll(getString(subclazz, nil), "\n<tr><td>", "\n<tr><td>&nbsp;&nbsp;<span>&gt;</span>")
			}
		} else {
			doc += fmt.Sprintf("\n<tr><td><span>%s</span></td><td>%s</td><td>%s</td><td>%s</td></tr>", item.name, "object", item.getOptionString(), item.remark)
			doc += strings.ReplaceAll(getString(field.Type, nil), "\n<tr><td>", "\n<tr><td>&nbsp;&nbsp;<span>&gt;</span>")
		}
		if fieldlist != nil {
			*fieldlist = append(*fieldlist, item)
		}
	}

	for i := 0; i < 100; i++ {
		tmp := strings.ReplaceAll(doc, "<span>&gt;</span>&nbsp;&nbsp;", "<span>&nbsp;</span>&nbsp;&nbsp;")
		if len(tmp) == len(doc) {
			return doc
		}
		doc = tmp
	}

	return doc
}

func getDocumentList() []*document {
	list := make([]*document, 0, len(docmap))
	for _, doc := range docmap {
		list = append(list, doc)
	}
	sort.Slice(list, func(i, j int) bool {
		return list[i].path < list[j].path
	})
	return list
}

func getDocument(path string) *document {
	return docmap[FormatPath(path)]
}

func newFieldItem(field reflect.StructField) fieldItem {
	item := fieldItem{clazz: field.Type, name: field.Name, required: true}
	val, ok := field.Tag.Lookup("required")
	if ok && len(val) > 0 {
		if strings.EqualFold(val, "false") {
			item.required = false
		} else if strings.EqualFold(val, "true") {
			item.required = true
		} else {
			item.required = true
			item.remark = val
		}
	} else {
		val, ok = field.Tag.Lookup("optional")
		if ok && len(val) > 0 {
			if strings.EqualFold(val, "false") {
				item.required = true
			} else if strings.EqualFold(val, "true") {
				item.required = false
			} else {
				item.required = false
				item.remark = val
			}
		}
	}

	val, ok = field.Tag.Lookup("json")
	if ok && len(val) > 0 {
		item.name = val
	}

	val, ok = field.Tag.Lookup("form")
	if ok && len(val) > 0 {
		item.name = val
	}

	val, ok = field.Tag.Lookup("regex")
	if ok && len(val) > 0 {
		item.regex = regexp.MustCompile(val)
	}

	val, ok = field.Tag.Lookup("remark")
	if ok && len(val) > 0 {
		item.remark = val
	}

	val, ok = field.Tag.Lookup("option")
	if ok && len(val) > 0 {
		vec := strings.Split(val, ",")
		for _, v := range vec {
			if v = strings.TrimSpace(v); len(v) > 0 {
				item.option = append(item.option, v)
			}
		}
	}

	val, ok = field.Tag.Lookup("range")
	if ok && len(val) > 0 {
		str := strings.HasPrefix(val, "(")
		end := strings.HasSuffix(val, ")")
		if str && end {
			item.rangeT = 1
		} else if str {
			item.rangeT = 2
		} else if end {
			item.rangeT = 3
		} else {
			item.rangeT = 4
		}
		val = strings.Trim(val, "[()]")
		if strings.Contains(val, ".") || strings.HasPrefix(item.clazz.Name(), "float") {
			item.rangeT = -item.rangeT
		}

		arr := strings.Split(val, ",")
		if len(arr) == 2 {
			if item.rangeT > 0 {
				item.rangeX = errno.IgnoreError(utils.ToInteger[int64](arr[0]))
				item.rangeY = errno.IgnoreError(utils.ToInteger[int64](arr[1]))
			} else {
				item.rangeXF = errno.IgnoreError(utils.ToFloat(arr[0]))
				item.rangeYF = errno.IgnoreError(utils.ToFloat(arr[1]))
			}
		} else {
			item.rangeT = 0
		}
	}

	return item
}

func addDocument[RequestType any, ResponseType any](path string) *document {
	var req RequestType
	var rsp ResponseType
	var doc = &document{
		path:         path,
		requestType:  reflect.TypeOf(req),
		responseType: reflect.TypeOf(rsp),
	}
	doc.request, doc.response = getString(doc.requestType, &doc.fieldList), getString(doc.responseType, nil)
	docmap[doc.path] = doc
	return doc
}
